package edu.hda.pt.server.connections.xsocket;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;

import org.xsocket.DataConverter;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.IConnection.FlushMode;

import edu.hda.pt.client.Config;
import edu.hda.pt.server.connections.PTServerXSocket;
import edu.hda.pt.utils.Stream;
import edu.hda.pt.utils.TestObject;
import edu.hda.pt.utils.Stream.StreamState;

/**
 * Event handler for the xSocket server
 * 
 * @author Marc Hammerton
 * 
 */
public class ServerHandler implements IConnectHandler, IDataHandler {

	private PTServerXSocket server;

	/**
	 * Constructor setting the server object
	 * 
	 * @param server
	 *            - the server object
	 */
	public ServerHandler(PTServerXSocket server) {
		this.server = server;
	}

	/**
	 * This method is called when a new connection is established and sets
	 * connection settings.
	 * 
	 * @see org.xsocket.connection.IConnectHandler#onConnect(org.xsocket.connection.INonBlockingConnection)
	 */
	@Override
	public boolean onConnect(INonBlockingConnection nbc) throws IOException,
			BufferUnderflowException, MaxReadSizeExceededException {

		nbc.setAutoflush(false);
		nbc.setFlushmode(FlushMode.ASYNC);

		return false;
	}

	/**
	 * This method is called when a message from the client is received. It
	 * calls the appropriate server method and sends the response back to the
	 * client.
	 * 
	 * @seeorg.xsocket.connection.IDataHandler#onData(org.xsocket.connection. 
	 *                                                                        INonBlockingConnection
	 *                                                                        )
	 */
	@Override
	public boolean onData(INonBlockingConnection connection)
			throws IOException, BufferUnderflowException,
			ClosedChannelException, MaxReadSizeExceededException {

		short type = -1;
		int dataLength = 0;
		ByteBuffer[] buffers = null;

		// read data
		connection.markReadPosition();
		try {
			type = connection.readShort();

			if (type == Config.HANDLEBYTES || type == Config.HANDLEOBJECT
					|| type == Config.HANDLESTRING) {
				dataLength = connection.readInt();
			} else if (type == Config.HANDLEBOOL) {
				dataLength = 1;
			} else if (type == Config.HANDLEINTEGER) {
				dataLength = 4;
			} else if (type == Config.HANDLELONG) {
				dataLength = 8;
			} else if (type == Config.HANDLESTREAM_START) {
				dataLength = 4;
			} else if (type == Config.HANDLESTREAM_STARTED) {
				dataLength = 0;
			} else if (type == Config.HANDLESTREAM_STOP) {
				dataLength = 0;
			}

			buffers = connection.readByteBufferByLength(dataLength);

			connection.removeReadMark();

		} catch (BufferUnderflowException bue) {
			connection.resetToReadMark();
			return true;
		}

		if (type == Config.HANDLEBOOL) {
			boolean value = buffers[0].get() == 1 ? true : false;
			boolean response = server.handleBool(value);
			connection.write(response == true ? (byte) 1 : (byte) 0);
			connection.flush();
		} else if (type == Config.HANDLEBYTES) {
			byte[] value = DataConverter.toBytes(buffers);

			byte[] response = server.handleBytes(value);
			connection.write(response.length);
			connection.write(response);
			connection.flush();
		} else if (type == Config.HANDLEINTEGER) {
			ByteBuffer temp = DataConverter.toByteBuffer(buffers);
			int value = temp.getInt();

			int response = server.handleInteger(value);
			connection.write(response);
			connection.flush();
		} else if (type == Config.HANDLELONG) {
			ByteBuffer temp = DataConverter.toByteBuffer(buffers);
			long value = temp.getLong();

			long response = server.handleLong(value);
			connection.write(response);
			connection.flush();
		} else if (type == Config.HANDLEOBJECT) {
			byte[] bytes = DataConverter.toBytes(buffers);

			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);
			TestObject value = null;
			try {
				value = (TestObject) ois.readObject();
			} catch (ClassNotFoundException e) {
				System.out.println(server.serverID + " Exception: ");
				e.printStackTrace();
			}
			ois.close();
			bis.close();

			TestObject objectResponse = server.handleObject(value);

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(objectResponse);
			oos.flush();
			oos.close();
			bos.close();
			byte[] response = bos.toByteArray();

			connection.write(response.length);
			connection.write(response);
			connection.flush();
		} else if (type == Config.HANDLESTRING) {
			String value = DataConverter.toString(buffers);

			String stringResponse = server.handleString(value);
			byte[] response = stringResponse.getBytes();
			connection.write(response.length);
			connection.write(response);
			connection.flush();
		} else if (type == Config.HANDLESTREAM_START) {
			ByteBuffer temp = DataConverter.toByteBuffer(buffers);
			int fs = temp.getInt();
			Stream stream = new Stream(fs);
			stream.setState(StreamState.START);
			server.handleStream(stream, connection);
		} else if (type == Config.HANDLESTREAM_STOP) {
			Stream stream = new Stream();
			stream.setState(StreamState.FINISH);
			server.handleStream(stream, connection);
		}

		return true;
	}

}
